/*
 *     *********************************************************************
 *     * Copyright (C) 1988, 1990 Stanford University.                     *
 *     * Permission to use, copy, modify, and distribute this              *
 *     * software and its documentation for any purpose and without        *
 *     * fee is hereby granted, provided that the above copyright          *
 *     * notice appear in all copies.  Stanford University                 *
 *     * makes no representations about the suitability of this            *
 *     * software for any purpose.  It is provided "as is" without         *
 *     * express or implied warranty.  Export of this software outside     *
 *     * of the United States of America may require an export license.    *
 *     *********************************************************************
 */

#include <stdio.h>
#include <stdlib.h>
#include "defs.h"
#include "net.h"
#include "ASSERT.h"
#include "globals.h"

#ifdef FAULT_SIM
extern	int	fault_mode;
#define	DoingFault	(fault_mode != 0)
#else
#define	DoingFault	(0)
#define	hchange	head		/* for the compiler's sake */
#endif


public	hptr   freeHist = NULL;		/* list of free history entries */
public  hptr   last_hist;		/* pointer to dummy hist-entry that
					 * serves as tail for all nodes */
public	int    num_edges = 0;
public	int    num_punted = 0;
public	int    num_cons_punted = 0;

public	hptr   first_model;		/* first model entry */
private	hptr   curr_model;		/* ptr. to current model entry */

private	char   mem_msg[] = "*** OUT OF MEMORY:Will stop collecting history\n";


public	Ulong  max_time;

public void init_hist() {
   static HistEnt  dummy;
   static HistEnt  dummy_model;

   max_time = MAX_TIME;

   last_hist = &dummy;
   dummy.next = last_hist;
   dummy.time = max_time;
   dummy.val = X;
   dummy.inp = 1;
   dummy.punt = 0;
   dummy.t.r.delay = dummy.t.r.rtime = 0;

   dummy_model.time = 0;
   dummy_model.val = model_num;
   dummy_model.inp = 0;
   dummy_model.punt = 0;
   dummy_model.next = NULL;
   first_model = curr_model = &dummy_model;
}


#define	NEW_HIST( NEW, ACTnoMEM )					\
  {									\
    if( ((NEW) = freeHist) == NULL )					\
      {									\
	if( ((NEW) = (hptr) MallocList( sizeof( HistEnt ), 0 )) == NULL ) \
	  {								\
	    lprintf( stderr, mem_msg );					\
	    sm_stat |= OUT_OF_MEM;					\
	    ACTnoMEM;							\
	  }								\
      }									\
    freeHist = (NEW)->next;						\
  }									\
 

public void SetFirstHist( nptr node, int value, int inp, long time ) {
   node->head.time = time;
   node->head.val = value;
   node->head.inp = inp;
   node->head.t.r.rtime = node->head.t.r.delay = 0;
}


/*
 * Add a new entry to the history list.  Update curr to point to this change.
 */
public void AddHist( nptr node, int value, int inp, long time, long delay, long rtime ) {
   register hptr  newh, curr;

   num_edges++;

   curr = node->curr;

   if( sm_stat & OUT_OF_MEM )
      return;

   while( curr->next->punt )		/* skip past any punted events */
      curr = curr->next;

   NEW_HIST( newh, return );

   newh->next = curr->next;
   newh->time = time;
   newh->val = value;
   newh->inp = inp;
   newh->punt = 0;
   newh->t.r.delay = delay;
   newh->t.r.rtime = rtime;
   node->curr = curr->next = newh;
}


#define	PuntTime( H )		( (H)->time - (H)->t.p.ptime )


/*
 * Add a punted event to the history list for the node.  Consecutive punted
 * events are kept in punted-order, so that h->ptime < h->next->ptime.
 * Adding a punted event does not change the current pointer, which always
 * points to the last "effective" node change.
 */
public void AddPunted( nptr node, evptr ev, long tim ) {
   register hptr  newp, h;

   h = node->curr;

   num_punted++;
   if( ( sm_stat & OUT_OF_MEM ) or DoingFault )
      return;

   NEW_HIST( newp, return );		/* allocate the punted event itself */

   newp->time = ev->ntime;
   newp->val = ev->eval;
   newp->inp = 0;
   newp->punt = 1;
   newp->t.p.delay = ev->delay;
   newp->t.p.rtime = ev->rtime;
   newp->t.p.ptime = newp->time - tim;

   if( h->next->punt ) {	/* there are some punted events already */
      num_cons_punted++;
      do {
         h = h->next;
      } while( h->next->punt );
   }

   newp->next = h->next;
   h->next = newp;
}


/*
 * Free up a node's history list
 */
public void FreeHistList( nptr node ) {
   register hptr  h, next;

   if( ( h = node->head.next ) == last_hist )		/* nothing to do */
      return;

   while( ( next = h->next ) != last_hist )		/* find last entry */
      h = next;

   h->next = freeHist;				/* link list to free list */
   freeHist = node->head.next;

   node->head.next = last_hist;
   node->curr = &( node->head );

   sm_stat &= ~OUT_OF_MEM;
}



public void NoMoreIncSim() {
   lprintf( stderr, "*** can't continue incremetal simulation\n" );
   exit( 1 );		/* do something here ? */
}


/*
 * Add a new model entry, recording the time of the change.
 */
public void NewModel( int nmodel ) {
   if( curr_model->time != cur_delta ) {
      hptr newh;

      NEW_HIST( newh, NoMoreIncSim() );

      newh->next = NULL;
      newh->time = cur_delta;
      newh->val = nmodel;
      curr_model->next = newh;
      curr_model = newh;
   } else
      curr_model->val = nmodel;
}



#define	QTIME( H )		( (H)->time - (H)->t.r.delay )

/*
 * Add a new change to the history list of node 'nd' caused by event 'ev'.
 * Skip past any punted events already in the history and update nd->curr to
 * point to the new change.
 */
public void NewEdge( nptr nd, evptr ev ) {
   register hptr  p, h, newh;

   for( p = nd->curr, h = p->next; h->punt; p = h, h = h->next );

   if( DoingFault )
      p = newh = &nd->hchange;
   else
      NEW_HIST( newh, NoMoreIncSim() );

   newh->time = ev->ntime;
   newh->val = ev->eval;
   newh->inp = 0;		/* always true in incremental simulation */
   newh->punt = 0;
   newh->t.r.delay = ev->delay;
   newh->t.r.rtime = ev->rtime;

   p->next = newh;
   newh->next = h;

   nd->curr = newh;		/* newh becomes the current value */
}


/*
 * Delete the next effective change (following nd->curr) from the history.
 * Punted events before the next change (in nd->t.punts) can now be freed.
 * Punted events following the deleted edge are moved to nd->t.punts.
 */
public void DeleteNextEdge( nptr nd ) {
   register hptr  a, b, c;

   if( DoingFault ) {
      if( nd->t.punts != NULL ) lprintf( stderr, "non-null punts\n" );

      if( nd->curr != &nd->hchange ) {
         nd->hchange = *nd->curr;
         nd->curr = &nd->hchange;
      }

      a = nd->curr;
      for( b = a->next; b->punt; a = b, b = b->next );

      nd->hchange.next = b->next;	/* b->next => punt before next edge */
      nd->t.punts = NULL;
      return;
   }

   if( ( a = nd->t.punts ) != NULL ) {	/* remove previously punted events */
      for( b = a; b->next != NULL; b = b->next );
      b->next = freeHist;
      freeHist = a;
   }

   a = nd->curr;
   for( b = a->next; b->punt; a = b, b = b->next );
   for( c = b->next; c->punt; b = c, c = c->next );
   c = a->next;			/* c => next edge */
   a->next = b->next;
   a = c->next;			/* a => first punted event after c */

   c->next = freeHist;			/* free the next effective change */
   freeHist = c;

   if( a->punt ) {		/* move punted events from hist */
      nd->t.punts = a;
      b->next = NULL;
   } else
      nd->t.punts = NULL;
}


#define	NEXTH( H, P )	for( (H) = (P)->next; (H)->punt; (H) = (H)->next )

public void FlushHist( Ulong ftime ) {
   register nptr  n;
   register hptr  h, p, head;

   for( n = GetNodeList(); n != NULL; n = n->n.next ) {
      head = &( n->head );
      if( head->next == last_hist or ( n->nflags & ALIAS ) )
         continue;
      p = head;
      NEXTH( h, p );
      while( h->time < ftime ) {
         p = h;
         NEXTH( h, h );
      }
      head->val = p->val;
      head->time = p->time;
      head->inp = p->inp;
      while( p->next != h )
         p = p->next;
      if( head->next != h ) {
         p->next = freeHist;
         freeHist = head->next;
         head->next = h;
      }
      if( n->curr->time < ftime ) {
         n->curr = head;
      }
   }
}


public int backToTime( nptr nd, char * unused ) {
   register hptr  h, p;

   if( nd->nflags & ( ALIAS | MERGED ) )
      return( 0 );

   h = &( nd->head );
   NEXTH( p, h );
   while( p->time < cur_delta ) {
      h = p;
      NEXTH( p, p );
   }
   nd->curr = h;

   /* queue pending events */
   for( p = h, h = p->next; ; p = h, h = h->next ) {
      long  qtime;

      if( h->punt ) {
         if( PuntTime( h ) < cur_delta )	/* already punted, skip it */
            continue;

         qtime = h->time - h->t.p.delay;	/* pending, enqueue it */
         if( qtime < cur_delta ) {
            Ulong tmp = cur_delta;
            cur_delta = qtime;
            
            enqueue_event( nd, ( int ) h->val, ( long ) h->t.p.delay, ( long ) h->t.p.rtime );
            cur_delta = tmp;
         }
         p->next = h->next;
         h->next = freeHist;
         freeHist = h;
         h = p;
      } else {
         qtime = QTIME( h );
         if( qtime < cur_delta ) {	/* pending, enqueue it */
            Ulong tmp = cur_delta;
            cur_delta = qtime;
            enqueue_event( nd, ( int ) h->val, ( long ) h->t.r.delay,
                           ( long ) h->t.r.rtime );
            cur_delta = tmp;

            p->next = h->next;		/* and free it */
            h->next = freeHist;
            freeHist = h;
            h = p;
         } else
            break;
      }
   }

   p->next = last_hist;
   p = h;
   /* p now points to the 1st event in the future (to be deleted) */
   if( p != last_hist ) {
      while( h->next != last_hist )
         h = h->next;
      h->next = freeHist;
      freeHist = p;
   }

   h = nd->curr;
   nd->npot = h->val;
   nd->c.time = h->time;
   if( h->inp )
      nd->nflags |= INPUT;

   if( nd->ngate != NULL ) {	/* recompute transistor states */
      register lptr  l;
      register tptr  t;

      for( l = nd->ngate; l != NULL; l = l->next ) {
         t = l->xtor;
         t->state = compute_trans_state( t );
      }
   }
   return( 0 );
}
